/*
	实现http server与Getter
	http请求地址：/<basepath>/<groupname>/<key>
*/

package geecache

import (
	"fmt"
	"geecache/geecache/consistenthash"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
	"sync"

	pb "geecache/geecachepb"

	"google.golang.org/protobuf/proto"
)

// 默认的basePath为"/_geecache/"
const defaultBasePath = "/_geecache/"
const defaultReplicas = 50

type HTTPPool struct {
	self        string // 记录自己的地址：端口
	basepath    string
	peers       *consistenthash.Map    // 一致性哈希记录key与节点的对应关系
	httpGetters map[string]*httpGetter // 记录每一个节点与其对应的httpGetter
	mu          sync.Mutex
}

// 新建HTTPPool
// self: 监听地址与端口
func NewHTTPPool(self string) *HTTPPool {
	return &HTTPPool{
		self:     self,
		basepath: defaultBasePath,
	}
}

// Log方法
// 包装log.Printf
func (h *HTTPPool) Log(format string, v ...interface{}) {
	log.Printf("[server - %s]: %s", h.self, fmt.Sprintf(format, v...))
}

// ServeHTTP
func (h *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 判断路径是否包含basePath
	if !strings.HasPrefix(r.URL.Path, h.basepath) {
		panic("HTTPPool serving unexpected path: " + r.URL.Path)
	}
	h.Log("%s %s", r.Method, r.URL.Path)

	// 分割访问路径，获得groupName和key
	part := strings.SplitN(r.URL.Path[len(h.basepath):], "/", 2)
	if len(part) != 2 {
		http.Error(w, "bad request", http.StatusBadRequest)
		return
	}

	groupName := part[0]
	key := part[1]

	// 获取Group
	group := GetGroup(groupName)
	if group == nil {
		http.Error(w, "no such group", http.StatusNotFound)
		return
	}

	// 调用Get
	bv, err := group.Get(key)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// 写返回值
	body, err := proto.Marshal(&pb.Response{Value: bv.ByteSlice()})
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/octet-stream")
	w.Write(body)
}

// 获取key对应的远程节点
func (h *HTTPPool) PickPeer(key string) (PeerGetter, bool) {
	h.mu.Lock()
	defer h.mu.Unlock()

	// 获取node
	peer := h.peers.Get(key)
	if peer != "" && peer != h.self {
		h.Log("Pick peer %s", peer)
		return h.httpGetters[peer], true
	}

	return nil, false
}

// 设置远程节点
func (h *HTTPPool) Set(peers ...string) {
	h.mu.Lock()
	defer h.mu.Unlock()

	h.peers = consistenthash.New(defaultReplicas, nil)
	h.peers.Add(peers...)

	h.httpGetters = make(map[string]*httpGetter, len(peers))
	for _, peer := range peers {
		h.httpGetters[peer] = &httpGetter{
			baseUrl: peer + defaultBasePath,
		}
	}
}

// 从远程节点获取数据
type httpGetter struct {
	baseUrl string // 远程节点地址+basePath
}

// 实现PeerGetter方法
// 使用http中Get方法
func (g *httpGetter) Get(in *pb.Request, out *pb.Response) error {
	// 拼接URL
	u := fmt.Sprintf("%v%v/%v",
		g.baseUrl,
		url.QueryEscape(in.GetGroup()),
		url.QueryEscape(in.GetKey()),
	)

	// 调用GET
	res, err := http.Get(u)
	if err != nil {
		fmt.Println("get from remote nodes error: ", err)
		return err
	}

	defer res.Body.Close()

	// 判断返回值
	if res.StatusCode != http.StatusOK {
		return fmt.Errorf("status not ok: %v", res.StatusCode)
	}

	buf, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return fmt.Errorf("read from body error: %v", err)
	}

	if err = proto.Unmarshal(buf, out); err != nil {
		return fmt.Errorf("decoding response body: %v", err)
	}

	// 定长
	// buf2 := make([]byte, 512)
	// bufReader := bufio.NewReader(res.Body)
	// bufReader.Read(buf2)

	return nil
}

var _ PeerGetter = (*httpGetter)(nil)
var _ PeerPicker = (*HTTPPool)(nil)
